<!DOCTYPE html>
<html lang="zh-cn">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>成三棋</title>
    <style>
        * {
            padding: 0;
            margin: 0;
        }

        html,
        body {
            background-color: #000;
        }

        #canvas {
            width: 600px;
            height: 600px;
        }

        #message {
            color: white;
            position: fixed;
            top: 50px;
            left: 50%;
            transform: translateX(-50%);
            padding: 5px 10px;
            font-size: 14px;
            border-radius: 6px;
            background-color: rgba(255, 255, 255, .5);
            opacity: 0;
            pointer-events: none !important;
            transition: all .5s ease-in-out;
        }
    </style>
</head>

<body>
    <div id="message">123</div>
    <canvas id="canvas"></canvas>
    <!-- <script src="./src/index.js"></script> -->
    <script>

        const boardSize = 600;
        const margin = 30; // 边距

        let canvas, ctx, points, pieces = [], gap = boardSize / 6;

        let currentPlayer = 1//白色
        let isFinished = false
        const piecesMaxLength = 2*9
        // 开始阶段各自落子是否结束
        let placePiceFinished = false

        let movePiece = null

        // 横线段
        const hLines = [
            [0, 0, boardSize, 0],
            [gap, gap, boardSize - gap, gap],
            [boardSize / 3, boardSize / 3, boardSize - boardSize / 3, boardSize / 3],
            [boardSize / 3, (boardSize / 3) * 2, boardSize - boardSize / 3, (boardSize / 3) * 2],
            [gap, gap * 5, boardSize - gap, gap * 5],
            [0, boardSize, boardSize, boardSize]
        ];

        // 纵线段
        const vLines = [
            [0, 0, 0, boardSize],
            [gap, gap, gap, boardSize - gap],
            [boardSize / 3, boardSize / 3, boardSize / 3, boardSize - boardSize / 3],
            [2 * boardSize / 3, 2 * boardSize / 3, 2 * boardSize / 3, boardSize - 2 * boardSize / 3],
            [gap * 5, gap, gap * 5, boardSize - gap],
            [boardSize, 0, boardSize, boardSize]
        ];

        const midLines = [
            [boardSize / 2, 0, boardSize / 2, gap * 2],
            [boardSize / 2, boardSize - gap * 2, boardSize / 2, boardSize],
            [0, boardSize / 2, gap * 2, boardSize / 2],
            [boardSize - gap * 2, boardSize / 2, boardSize, boardSize / 2]
        ];

        const uuid = () => Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
        class Piece {
            constructor(x, y, color, selected, canEat) {
                this.x = x;
                this.y = y;
                this.color = color;
                this.selected = selected
                this.canEat = canEat
                this.id = uuid()
            }
            draw() {
                const radius = 15;

                // 创建一个线性渐变，从左上到右下，用于模拟侧边阴影
                let sideGradient = ctx.createLinearGradient(this.x - radius, this.y - radius, this.x + radius, this.y + radius);
                sideGradient.addColorStop(0, 'rgba(0,0,0,0.3)');
                sideGradient.addColorStop(1, 'rgba(0,0,0,0)'); // 渐隐到透明

                // 创建一个径向渐变，从中心向外，用于制造顶部高光
                let topGradient = ctx.createRadialGradient(this.x, this.y, 0, this.x, this.y, radius);
                topGradient.addColorStop(0, this.color); // 中心颜色
                topGradient.addColorStop(0.9, 'rgba(255,255,255,0.8)'); // 高光部分
                topGradient.addColorStop(1, this.color); // 边缘回到主体颜色

                // 设置阴影以增加立体感
                ctx.shadowColor = 'rgba(0,0,0,0.4)';
                ctx.shadowOffsetX = 4;
                ctx.shadowOffsetY = 4;
                ctx.shadowBlur = 6;

                // 开始绘制
                ctx.fillStyle = sideGradient;
                ctx.beginPath();
                ctx.arc(this.x, this.y, radius, 0, Math.PI * 2);
                ctx.fill();
                ctx.globalCompositeOperation = 'source-atop'; // 改变混合模式，使高光只出现在已有的图形上
                ctx.fillStyle = topGradient;
                // ctx.closePath();
                // ctx.beginPath();
                // 恢复默认混合模式
                ctx.globalCompositeOperation = 'source-over';

                // 清除阴影设置
                ctx.shadowColor = 'rgba(0,0,0,0)';
                ctx.arc(this.x, this.y, radius, 0, Math.PI * 2);
                ctx.fill();
                ctx.closePath();

                // 选中样式
                if (this.selected) {
                    ctx.strokeStyle = 'rgba(255,0,255,1)';
                    ctx.lineWidth = 2;
                    ctx.stroke();
                }
                // 被吃的样式
                if (this.canEat) {
                    ctx.strokeStyle = 'rgba(255,0,0,1)';
                    ctx.lineWidth = 2;
                    ctx.stroke();
                }
            }
        }

        const init = () => {
            canvas = document.getElementById("canvas");
            ctx = canvas.getContext("2d");
            canvas.width = boardSize + margin * 2;
            canvas.height = boardSize + margin * 2;
            ctx.fillStyle = "#000";
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            drawBoard(boardSize, ctx, margin);
            canvas.addEventListener("click", onCanvasClick);
        }

        init();

        // 判断三点是否共线
        function areCollinear(p1, p2, p3) {
            return (p2.y - p1.y) * (p3.x - p1.x) === (p3.y - p1.y) * (p2.x - p1.x);
        }

        function drawBoard(boardSize, ctx, margin) {
            const gap = boardSize / 6;
            ctx.strokeStyle = "#fff";
            ctx.lineWidth = 2;



            function drawLines(lines, isHorizontal = true) {
                lines.forEach(line => {
                    ctx.beginPath();
                    if (isHorizontal) {
                        ctx.moveTo(line[0] + margin, line[1] + margin);
                        ctx.lineTo(line[2] + margin, line[3] + margin);
                    } else {
                        ctx.moveTo(line[0] + margin, line[1] + margin);
                        ctx.lineTo(line[2] + margin, line[3] + margin);
                    }
                    ctx.stroke();
                });
            }

            drawLines(hLines, true);
            drawLines(vLines, false);
            drawLines(midLines, false);

            // -----------------------
            // 交点
            // -----------------------
            points = [
                { x: 0, y: 0 },
                { x: boardSize / 2, y: 0 },
                { x: boardSize, y: 0 },
                { x: gap, y: gap },
                { x: boardSize / 2, y: gap },
                { x: boardSize - gap, y: gap },
                { x: boardSize / 3, y: boardSize / 3 },
                { x: boardSize / 2, y: boardSize / 3 },
                { x: boardSize - boardSize / 3, y: boardSize / 3 },
                { x: boardSize / 3, y: (boardSize / 3) * 2 },
                { x: boardSize / 2, y: (boardSize / 3) * 2 },
                { x: boardSize - boardSize / 3, y: (boardSize / 3) * 2 },
                { x: gap, y: gap * 5 },
                { x: boardSize / 2, y: gap * 5 },
                { x: boardSize - gap, y: gap * 5 },
                { x: 0, y: boardSize },
                { x: boardSize / 2, y: boardSize },
                { x: boardSize, y: boardSize },
                { x: gap * 2, y: gap * 3 },
                { x: gap * 4, y: gap * 3 },
                { x: gap, y: gap * 3 },
                { x: boardSize - gap, y: gap * 3 },
                { x: 0, y: gap * 3 },
                { x: boardSize, y: gap * 3 }
            ];

            return points;
        }

        function onCanvasClick(event) {
            const rect = canvas.getBoundingClientRect();
            // 直接获取画布绝对坐标（无需减 margin）
            const clickX = event.clientX - rect.left;
            const clickY = event.clientY - rect.top;

            for (let i = 0; i < points.length; i++) {
                const p = points[i];
                const dx = clickX - p.x;
                const dy = clickY - p.y;
                const distance = Math.sqrt(dx * dx + dy * dy);
                if (distance - margin <= margin) { // 棋子半径范围内
                    // 检查该点是否已有棋子
                    const occupied = pieces.find(piece =>
                        piece instanceof Piece &&
                        piece.x === p.x + margin &&
                        piece.y === p.y + margin
                    );
                    if (!occupied) {
                        // 是否对方等待吃子
                        const enemyWaiting = pieces.find(piece => piece.canEat);
                        if (enemyWaiting) {
                            return
                        }
                        // 落子：直接用绝对坐标绘制
                        const piece = new Piece(p.x + margin, p.y + margin, currentPlayer === 1 ? "#fff" : "#f40");
                        // 落子前，判定是直接落子，还是走子
                        const moving = pieces.find(piece => piece.selected);
                        if (moving) {
                            // 校验落子位置是否合法
                            const valid = checkPositionValid(moving, piece);
                            if (!valid) {
                                return showMessage('请选择正确的位置')
                            }
                        }
                        if(!placePiceFinished){
                            // 添加棋子
                            pieces.push(piece);
                            piece.draw();
                            currentPlayer = 3 - currentPlayer;
                            const win = checkSingleMoveWin(piece)
                        }                        
                        if (placePiceFinished) {
                            if (movePiece) {
                                const newPiece = new Piece(p.x + margin, p.y + margin, movePiece.color)
                                pieces.push(newPiece)
                                newPiece.draw()
                                let index = pieces.findIndex(item => item.selected)
                                pieces.splice(index, 1)
                                const win = checkSingleMoveWin(newPiece)
                                reDraw()
                                currentPlayer = 3 - currentPlayer;
                            }
                        }
                    } else {
                        // 如果点击的棋子有 canEat 属性                        
                        if (occupied.canEat) {
                            eatPiece(occupied)                            
                            return
                        }
                        if(!placePiceFinished) return 
                        // 选择要移动的棋子
                        choosePiece(occupied)
                    }
                    return;
                }
            }

        }

        function eatPiece(piece) {
            const index = pieces.findIndex(item => item.id === piece.id)
            pieces.splice(index, 1)
            pieces.forEach(piece => {
                piece.selected = false
                piece.canEat = false
            })        
            checkWin(piece)
            reDraw()
        }
        
        // 判定最终胜负

        function checkWin(piece) { 
            if(!placePiceFinished) return
            let leftPieces = pieces.filter(item => item.color === piece.color)
            if (leftPieces.length < 3) {
                alert(piece.color == '#fff'  ? '红棋胜利' : '白棋胜利')
            }
        }
        function reDraw() {
            ctx.clearRect(0, 0, canvas.width, canvas.height)
            drawBoard(boardSize, ctx, margin);
            drawPieces()

        }
        function drawPieces() {
            pieces.forEach(piece => piece.draw())
        }
        // 获取对方可以吃的棋子
        function getOpponentEatPieces(piece) {
            let eatPieces = []
            for (let i = 0; i < pieces.length; i++) {
                if (pieces[i].color !== piece.color) {
                    pieces[i].canEat = true
                    eatPieces.push(pieces[i])
                }
            }
            reDraw()
            return eatPieces
        }
        function checkSingleMoveWin(piece) {
            console.log('checkSingleMoveWin')
            const toKey = (x, y) => `${x},${y}`;
            const getNbrs = (key) => neighbors[key] || [];

            const sameColor = new Set(
                pieces.filter(p => p.color === piece.color).map(p => toKey(p.x, p.y))
            );
            const startKey = toKey(piece.x, piece.y);

            // 只从当前 piece 出发，数两边连续棋子
            function countAxis(axis) {
                const isH = axis === 'h';
                let total = 1; // 包含自己
                // 向某个方向数
                function step(dir) {
                    let curKey = startKey;
                    while (true) {
                        const [cx, cy] = curKey.split(',').map(Number);
                        const nbrs = getNbrs(curKey);
                        const next = nbrs.find(n => {
                            if (isH) {
                                return n.y === cy && (dir > 0 ? n.x > cx : n.x < cx);
                            } else {
                                return n.x === cx && (dir > 0 ? n.y > cy : n.y < cy);
                            }
                        });
                        if (!next) break;
                        const nKey = toKey(next.x, next.y);
                        if (!sameColor.has(nKey)) break;
                        total++;
                        curKey = nKey;
                    }
                }

                step(1);  // 向右 / 向下
                step(-1); // 向左 / 向上

                return total;
            }

            const win = countAxis('h') >= 3 || countAxis('v') >= 3;
            if (win) {
                getOpponentEatPieces(piece);
            }
            return win;
        }



        // 选中点击的棋子
        function choosePiece(occupied) {
            console.log('选中棋子', occupied)
            console.log('当前玩家', currentPlayer)
            const currentFlag = occupied.color === '#fff' ? 1: 2
            if (currentFlag != currentPlayer) {
                console.log('-----')
                return showMessage('不符合行动规则')
            }
            // 如果有要吃的棋子 && 当前选中的棋子不是吃掉的棋子
            const eatPieces = pieces.filter(item => item.canEat)
            if (eatPieces.length && eatPieces[0].color !== occupied.color) {
                return
            }
            let index = pieces.findIndex(item => item.id === occupied.id)
            pieces.splice(index, 1)
            pieces.forEach(piece => {
                piece.selected = false
            })
            // 选中棋子
            const newPiece = new Piece(occupied.x, occupied.y, occupied.color, true)
            pieces.push(newPiece)
            movePiece = newPiece
            reDraw()
        }
        // 校验当前棋子目标位置 是否合法
        function checkPositionValid(moving, target) {
            const { x, y } = moving
            const { x: tx, y: ty } = target
            const near = getNearPosition(moving)
            for (let i = 0; i < near.length; i++) {
                const { x: nx, y: ny } = near[i]
                if (nx === tx && ny === ty) {
                    return true
                }
            }
            return false
        }
        // 获取临近的位置
        function getNearPosition(current) {
            const { x, y } = current
            const near = getNeighbors(x, y)
            return near
        }

        // 构建邻接表
        function buildNeighbors(points, hLines, vLines, midLines, margin) {
            const neighbors = {};

            // 初始化每个点
            points.forEach(p => {
                const key = `${p.x + margin},${p.y + margin}`;
                neighbors[key] = [];
            });

            // 辅助函数：给两个点互相加邻居
            function addNeighbor(p1, p2) {
                const k1 = `${p1.x + margin},${p1.y + margin}`;
                const k2 = `${p2.x + margin},${p2.y + margin}`;
                if (!neighbors[k1].some(p => p.x === p2.x + margin && p.y === p2.y + margin)) {
                    neighbors[k1].push({ x: p2.x + margin, y: p2.y + margin });
                }
                if (!neighbors[k2].some(p => p.x === p1.x + margin && p.y === p1.y + margin)) {
                    neighbors[k2].push({ x: p1.x + margin, y: p1.y + margin });
                }
            }

            // 遍历所有线段，建立连接关系
            [...hLines, ...vLines, ...midLines].forEach(line => {
                const [x1, y1, x2, y2] = line;
                // 找到这条线上所有落在 points 的点（即交点）
                const linePoints = points.filter(p =>
                    (x1 === x2 && p.x === x1 && p.y >= Math.min(y1, y2) && p.y <= Math.max(y1, y2)) ||
                    (y1 === y2 && p.y === y1 && p.x >= Math.min(x1, x2) && p.x <= Math.max(x1, x2))
                );

                // 邻居只取相邻的点（按顺序两两相连）
                linePoints.sort((a, b) => a.x - b.x || a.y - b.y);
                for (let i = 0; i < linePoints.length - 1; i++) {
                    addNeighbor(linePoints[i], linePoints[i + 1]);
                }
            });
            return neighbors;
        }

        // 使用示例
        const neighbors = buildNeighbors(points, hLines, vLines, midLines, margin);

        // 查询某个点的邻居
        function getNeighbors(x, y) {
            return neighbors[`${x},${y}`] || [];
        }
        // 提示信息
        function showMessage(message) {
            const oMessage = document.getElementById('message');
            oMessage.innerHTML = message;
            oMessage.style.opacity = 1;
            setTimeout(() => {
                oMessage.style.opacity = 0;
            }, 2000);
        }

        function WatchPieces(){
            return new Proxy(pieces, {
                set(target, key, value) {
                    console.log(`${key} was set to ${value}`);
                    return true;
                }
            });
        }
        const handler = {
            get(target, prop, receiver) {
                const value = Reflect.get(target, prop, receiver);
                // 如果是函数（如 push, pop），返回一个包装函数
                if (typeof value === 'function') {
                    return function (...args) {
                        const result = value.apply(target, args);
                        if(target.length >= piecesMaxLength && !placePiceFinished){
                            // 落子阶段已结束
                            showMessage('落子已结束，开始行棋')
                            placePiceFinished = true
                        }
                        return result;
                    }
                }
                return value;
            },
            set(target, prop, value, receiver) {
                const oldValue = target[prop];
                const result = Reflect.set(target, prop, value, receiver);
                return result;
            }
        };
        pieces = new Proxy(pieces, handler);
    </script>
</body>

</html>